package org.budo.dubbo.protocol.http;

import java.util.HashMap;
import java.util.Map;

import org.budo.dubbo.protocol.http.cipher.Cipher;
import org.budo.dubbo.protocol.http.cipher.CipherUtil;
import org.budo.dubbo.protocol.http.exception.consumer.HttpRequestFailRuntimeException;
import org.budo.dubbo.protocol.http.invocation.BudoDubboRpcInvocation;
import org.budo.dubbo.protocol.http.sign.SignCheck;
import org.budo.dubbo.protocol.http.sign.SignEntity;
import org.budo.dubbo.protocol.http.util.BudoReflectionUtil;
import org.budo.dubbo.protocol.http.util.BudoSerializationUtil;
import org.budo.dubbo.serialize.util.DubboSerializeUtil;
import org.budo.support.http.client.Request;
import org.budo.support.http.client.Response;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.serialize.Serialization;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcInvocation;

/**
 * @author lmw
 * @see org.budo.dubbo.protocol.http.dispatcher.servlet.BudoDubboHttpProtocolDispatcherServlet
 * @see org.budo.dubbo.protocol.http.BudoDubboHttpProtocol
 * @see com.alibaba.dubbo.rpc.filter.ContextFilter
 */
public class BudoDubboHttpInvoker<T> extends AbstractBudoDubboHttpInvoker<T> {
    private static final Logger log = LoggerFactory.getLogger(BudoDubboHttpInvoker.class);

    private static final ExtensionLoader<Serialization> SERIALIZATION_EXTENSION_LOADER = ExtensionLoader.getExtensionLoader(Serialization.class);

    public BudoDubboHttpInvoker(Class<T> type, URL url) {
        this.setInterface(type);
        this.setUrl(url);
    }

    /**
     * @see com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker#invoke(Invocation)
     */
    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        try {
            Map<String, String> attachments = RpcContext.getContext().getAttachments();
            if (attachments != null) {
                RpcInvocation rpcInvocation = (RpcInvocation) invocation;
                rpcInvocation.addAttachmentsIfAbsent(attachments);
            }
            return this.doInvoke(invocation);
        } catch (Throwable e) {
            if (e instanceof RpcException) {
                throw (RpcException) e;
            }

            throw new RpcException(e);
        }
    }

    private Result doInvoke(Invocation invocation) throws Throwable {
        String serializationKey = this.getUrl().getParameter(Constants.SERIALIZATION_KEY, Consts.SerializationDefault);
        Serialization serialization = SERIALIZATION_EXTENSION_LOADER.getExtension(serializationKey);
        Invoker<?> invoker = invocation.getInvoker();

        String requestBody = DubboSerializeUtil.serialize(invocation, serialization);

        BudoDubboRpcInvocation budoInvocation = new BudoDubboRpcInvocation(invocation, requestBody);

        // 传输层加密
        Cipher cipher = BudoReflectionUtil.getMethodAnnotation(invoker.getInterface(), //
                invocation.getMethodName(), //
                invocation.getParameterTypes(), //
                Cipher.class);

        if (null != cipher && null != cipher.value() && !cipher.value().isEmpty()) {
            budoInvocation = this.getCipherService(cipher.value()).cipher(budoInvocation);
        }

        if (log.isDebugEnabled()) {
            log.debug("#74 doInvoke, budoInvocation=" + budoInvocation //
                    + ", serializationKey=" + serializationKey //
                    + ", serialization=" + serialization);
        }

        Map<String, Object> headerMap = new HashMap<String, Object>();

        // 公共 headers
        Map<String, String> commonHeaders = this.commonHeaders(serializationKey, invocation);
        headerMap.putAll(commonHeaders);

        // Sign headers
        SignCheck signCheck = BudoReflectionUtil.getMethodAnnotation(invoker.getInterface(), //
                invocation.getMethodName(), //
                invocation.getParameterTypes(), //
                SignCheck.class);
        if (null != signCheck && signCheck.value()) {
            SignEntity signHeaders = this.signHeaders(budoInvocation);
            headerMap.putAll(signHeaders.toMap()); // TODO 这些东西是否应该放到 Invocation#getAttachments() 里面去
        }

        // build request
        Request request = new Request();
        request.setMethod(Request.POST);

        String requestUrl = this.buildRequestUrl();
        request.setUrl(requestUrl);
        request.setHeaders(headerMap);

        String _requestBody = budoInvocation.getRequestBody();

        byte[] _requestBytes = _requestBody.getBytes(Consts.UTF_8);
        request.setRequestBody(_requestBytes);

        // do request
        Response response = request.execute();

        // if request error
        if (null == response.getStatus() || response.getStatus().intValue() != 200) {
            String message = "#104 Server returned " + (null == response ? response : response.toSimpleString());
            throw new HttpRequestFailRuntimeException(message);
        }

        // read response
        String headerResultTypeName = response.getHeader(Consts.HeaderKey.ResultType);
        Class headerResultType = BudoReflectionUtil.getResultType(headerResultTypeName);

        byte[] responseData = null;

        // 返回内容的解密
        if (null == cipher || null == cipher.value() || cipher.value().isEmpty()) {
            responseData = response.getResponseData(); // 明文返回
        } else {
            responseData = CipherUtil.decipher(this.getCipherService(cipher.value()), budoInvocation, response.getBody());
        }

        // deserialize
        Object resultEntity = BudoSerializationUtil.deserialize(serialization, responseData, headerResultType);

        if (log.isDebugEnabled()) {
            log.debug("#119 invocation=" + invocation //
                    + ", response=" + (null == response ? response : response.toSimpleString()) //
                    + ", resultEntity=" + resultEntity);
        }

        return this.processResult(resultEntity, invocation);
    }
}
